]> dgit.raspbian.org Git - gnome-shell.git/commitdiff
Add a new lock screen menu to combine volume network and power
authorGiovanni Campagna <gcampagna@src.gnome.org>
Sun, 26 Aug 2012 14:05:46 +0000 (16:05 +0200)
committerGiovanni Campagna <gcampagna@src.gnome.org>
Tue, 28 Aug 2012 23:13:23 +0000 (01:13 +0200)
The design has a combined volume-network-power indicator in the lock
screen, which when opened shows a volume slider. Implement it by abstracting
the volume menu into a PopupMenuSection, and by creating three StIcons
bound to the real ones.

https://bugzilla.gnome.org/show_bug.cgi?id=682540

js/Makefile.am
js/ui/panel.js
js/ui/panelMenu.js
js/ui/sessionMode.js
js/ui/status/lockScreenMenu.js [new file with mode: 0644]
js/ui/status/network.js
js/ui/status/power.js
js/ui/status/volume.js

index 92e0e59939cd33a7e0b3ee1a4396cf6db90d34ab..330098105664499e39aed952279575b83555c91d 100644 (file)
@@ -85,6 +85,7 @@ nobase_dist_js_DATA =         \
        ui/shellDBus.js         \
        ui/status/accessibility.js      \
        ui/status/keyboard.js   \
+       ui/status/lockScreenMenu.js     \
        ui/status/network.js    \
        ui/status/power.js      \
        ui/status/volume.js     \
index 943079f2d81b9b5552b522eaac4391046537be8d..631a0a11c79ae80e16df330a155d465594e02606 100644 (file)
@@ -908,7 +908,7 @@ const Panel = new Lang.Class({
                                                   reactive: true });
         this.actor._delegate = this;
 
-        this._statusArea = {};
+        this.statusArea = {};
 
         Main.overview.connect('shown', Lang.bind(this, function () {
             this.actor.add_style_class_name('in-overview');
@@ -1126,7 +1126,7 @@ const Panel = new Lang.Class({
     },
 
     addToStatusArea: function(role, indicator, position) {
-        if (this._statusArea[role])
+        if (this.statusArea[role])
             throw new Error('Extension point conflict: there is already a status indicator for role ' + role);
 
         if (!(indicator instanceof PanelMenu.Button))
@@ -1138,9 +1138,9 @@ const Panel = new Lang.Class({
         if (indicator.menu)
             this._menus.addMenu(indicator.menu);
 
-        this._statusArea[role] = indicator;
+        this.statusArea[role] = indicator;
         let destroyId = indicator.connect('destroy', Lang.bind(this, function(emitter) {
-            delete this._statusArea[role];
+            delete this.statusArea[role];
             emitter.disconnect(destroyId);
         }));
 
@@ -1155,7 +1155,7 @@ const Panel = new Lang.Class({
         if (this._dateMenu)
             this._dateMenu.setLockedState(locked);
 
-        for (let id in this._statusArea)
-            this._statusArea[id].setLockedState(locked);
+        for (let id in this.statusArea)
+            this.statusArea[id].setLockedState(locked);
     },
 });
index 29007bf2636b8db4d0a14fa4bcb135b95a70b452..3e48f6207173891c07f2457f653c0392682c33b2 100644 (file)
@@ -236,7 +236,8 @@ const SystemStatusButton = new Lang.Class({
         this._box = new St.BoxLayout({ style_class: 'panel-status-button-box' });
         this.actor.add_actor(this._box);
 
-        this.setIcon(iconName);
+        if (iconName)
+            this.setIcon(iconName);
     },
 
     addIcon: function(gicon) {
index 1cc4ec346e03a21aab773dc754000c289b372b8c..55cfed776cb85511a69b28b8f93ef6224906fe0d 100644 (file)
@@ -11,6 +11,7 @@ const STANDARD_STATUS_AREA_SHELL_IMPLEMENTATION = {
     'a11y': imports.ui.status.accessibility.ATIndicator,
     'volume': imports.ui.status.volume.Indicator,
     'battery': imports.ui.status.power.Indicator,
+    'lockScreen': imports.ui.status.lockScreenMenu.Indicator,
     'keyboard': imports.ui.status.keyboard.InputSourceIndicator,
     'userMenu': imports.ui.userMenu.UserMenuButton
 };
@@ -44,12 +45,13 @@ const _modes = {
              statusArea: {
                  order: [
                      'a11y', 'display', 'keyboard',
-                     'volume', 'battery', 'powerMenu'
+                     'volume', 'battery', 'lockScreen', 'powerMenu'
                  ],
                  implementation: {
                      'a11y': imports.ui.status.accessibility.ATIndicator,
                      'volume': imports.ui.status.volume.Indicator,
                      'battery': imports.ui.status.power.Indicator,
+                     'lockScreen': imports.ui.status.lockScreenMenu.Indicator,
                      'keyboard': imports.ui.status.keyboard.InputSourceIndicator,
                      'powerMenu': imports.gdm.powerMenu.PowerMenuButton
                  }
@@ -68,12 +70,13 @@ const _modes = {
                        extraStylesheet: null,
                        statusArea: {
                            order: [
-                               'a11y', 'keyboard', 'volume'
+                               'a11y', 'keyboard', 'volume', 'lockScreen',
                            ],
                            implementation: {
                                'a11y': imports.ui.status.accessibility.ATIndicator,
                                'keyboard': imports.ui.status.keyboard.XKBIndicator,
-                               'volume': imports.ui.status.volume.Indicator
+                               'volume': imports.ui.status.volume.Indicator,
+                               'lockScreen': imports.ui.status.lockScreenMenu.Indicator,
                         }
                 }
            },
@@ -92,7 +95,7 @@ const _modes = {
               statusArea: {
                   order: [
                       'input-method', 'a11y', 'keyboard', 'volume', 'bluetooth',
-                      'network', 'battery', 'userMenu'
+                      'network', 'battery', 'lockScreen', 'userMenu'
                   ],
                   implementation: STANDARD_STATUS_AREA_SHELL_IMPLEMENTATION
               }
diff --git a/js/ui/status/lockScreenMenu.js b/js/ui/status/lockScreenMenu.js
new file mode 100644 (file)
index 0000000..6f8b897
--- /dev/null
@@ -0,0 +1,62 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+
+const Clutter = imports.gi.Clutter;
+const GObject = imports.gi.GObject;
+const Lang = imports.lang;
+const St = imports.gi.St;
+
+const Main = imports.ui.main;
+const PanelMenu = imports.ui.panelMenu;
+const PopupMenu = imports.ui.popupMenu;
+const VolumeMenu = imports.ui.status.volume;
+
+const Indicator = new Lang.Class({
+    Name: 'LockScreenMenuIndicator',
+    Extends: PanelMenu.SystemStatusButton,
+
+    _init: function() {
+        this.parent(null, _("Volume, network, battery"));
+        this.actor.hide();
+
+        this._volume = Main.panel.statusArea.volume;
+        if (this._volume) {
+            this._volumeIcon = this.addIcon(null);
+            this._volume.mainIcon.bind_property('gicon', this._volumeIcon, 'gicon',
+                                                GObject.BindingFlags.SYNC_CREATE);
+            this._volume.mainIcon.bind_property('visible', this._volumeIcon, 'visible',
+                                                GObject.BindingFlags.SYNC_CREATE);
+
+            this._volumeControl = VolumeMenu.getMixerControl();
+            this._volumeMenu = new VolumeMenu.VolumeMenu(this._volumeControl);
+            this.menu.addMenuItem(this._volumeMenu);
+        }
+
+        this._network = Main.panel.statusArea.network;
+        if (this._network) {
+            this._networkIcon = this.addIcon(null);
+            this._network.mainIcon.bind_property('gicon', this._networkIcon, 'gicon',
+                                                 GObject.BindingFlags.SYNC_CREATE);
+            this._network.mainIcon.bind_property('visible', this._networkIcon, 'visible',
+                                                 GObject.BindingFlags.SYNC_CREATE);
+
+            this._networkSecondaryIcon = this.addIcon(null);
+            this._network.secondaryIcon.bind_property('gicon', this._networkSecondaryIcon, 'gicon',
+                                                      GObject.BindingFlags.SYNC_CREATE);
+            this._network.secondaryIcon.bind_property('visible', this._networkSecondaryIcon, 'visible',
+                                                      GObject.BindingFlags.SYNC_CREATE);
+        }
+
+        this._battery = Main.panel.statusArea.battery;
+        if (this._battery) {
+            this._batteryIcon = this.addIcon(null);
+            this._battery.mainIcon.bind_property('gicon', this._batteryIcon, 'gicon',
+                                                 GObject.BindingFlags.SYNC_CREATE);
+            this._battery.mainIcon.bind_property('visible', this._batteryIcon, 'visible',
+                                                 GObject.BindingFlags.SYNC_CREATE);
+        }
+    },
+
+    setLockedState: function(locked) {
+        this.actor.visible = locked;
+    }
+});
index a235beeca414cb1f39c79b743454e78af48b30c2..72cea774c73b6992cc6e1df9b270fe2e5824b462 100644 (file)
@@ -1570,9 +1570,10 @@ const NMApplet = new Lang.Class({
     _init: function() {
         this.parent('network-offline', _('Network'));
 
-        this._secondaryIcon = this.addIcon(new Gio.ThemedIcon({ name: 'network-vpn' }));
-        this._secondaryIcon.hide();
+        this.secondaryIcon = this.addIcon(new Gio.ThemedIcon({ name: 'network-vpn' }));
+        this.secondaryIcon.hide();
 
+        this._isLocked = false;
         this._client = NMClient.Client.new();
 
         this._statusSection = new PopupMenu.PopupMenuSection();
@@ -1681,12 +1682,8 @@ const NMApplet = new Lang.Class({
     },
 
     setLockedState: function(locked) {
-        // FIXME: more design discussion is needed before we can
-        // expose part of this menu
-
-        if (locked)
-            this.menu.close();
-        this.actor.reactive = !locked;
+        this._isLocked = locked;
+        this._syncNMState();
     },
 
     _ensureSource: function() {
@@ -2074,13 +2071,8 @@ const NMApplet = new Lang.Class({
     },
 
     _syncNMState: function() {
-        if (!this._client.manager_running) {
-            log('NetworkManager is not running, hiding...');
-            this.menu.close();
-            this.actor.hide();
-            return;
-        } else
-            this.actor.show();
+        this.mainIcon.visible = this._client.manager_running;
+        this.actor.visible = this.mainIcon.visible && !this._isLocked;
 
         if (!this._client.networking_enabled) {
             this.setIcon('network-offline');
@@ -2192,14 +2184,14 @@ const NMApplet = new Lang.Class({
             // only show a separate icon when we're using a wireless/3g connection
             if (mc._section == NMConnectionCategory.WIRELESS || 
                 mc._section == NMConnectionCategory.WWAN) {
-                this._secondaryIcon.icon_name = vpnIconName;
-                this._secondaryIcon.visible = true;
+                this.secondaryIcon.icon_name = vpnIconName;
+                this.secondaryIcon.show();
             } else {
                 this.setIcon(vpnIconName);
-                this._secondaryIcon.visible = false;
+                this.secondaryIcon.hide();
             }
         } else {
-            this._secondaryIcon.visible = false;
+            this.secondaryIcon.hide();
         }
 
         // cleanup stale signal connections
index 1c18b4d59c402cd838beb994996fbb50627ab8e4..195422fad36ad1ff20cde5012316eeeaf9972883 100644 (file)
@@ -56,6 +56,7 @@ const Indicator = new Lang.Class({
 
         this._proxy = new PowerManagerProxy(Gio.DBus.session, BUS_NAME, OBJECT_PATH);
 
+        this._isLocked = false;
         this._deviceItems = [ ];
         this._hasPrimary = false;
         this._primaryDeviceId = null;
@@ -77,9 +78,8 @@ const Indicator = new Lang.Class({
     },
 
     setLockedState: function(locked) {
-        if (locked)
-            this.menu.close();
-        this.actor.reactive = !locked;
+        this._isLocked = locked;
+        this._syncIcon();
     },
 
     _readPrimaryDevice: function() {
@@ -150,16 +150,20 @@ const Indicator = new Lang.Class({
         }));
     },
 
-    _devicesChanged: function() {
+    _syncIcon: function() {
         let icon = this._proxy.Icon;
-        if (icon) {
+        let hasIcon = (icon != null);
+
+        if (hasIcon) {
             let gicon = Gio.icon_new_for_string(icon);
             this.setGIcon(gicon);
-            this.actor.show();
-        } else {
-            this.menu.close();
-            this.actor.hide();
         }
+        this.mainIcon.visible = hasIcon;
+        this.actor.visible = hasIcon && !this._isLocked;
+    },
+
+    _devicesChanged: function() {
+        this._syncIcon();
         this._readPrimaryDevice();
         this._readOtherDevices();
     }
index 550788776055e9f8bd367ef31487c961a05e9198..ab383dc4cdcdc03675d5fc9ae08c6ce8e6b8491c 100644 (file)
@@ -12,14 +12,27 @@ const VOLUME_ADJUSTMENT_STEP = 0.05; /* Volume adjustment step in % */
 
 const VOLUME_NOTIFY_ID = 1;
 
-const Indicator = new Lang.Class({
-    Name: 'VolumeIndicator',
-    Extends: PanelMenu.SystemStatusButton,
+// Each Gvc.MixerControl is a connection to PulseAudio,
+// so it's better to make it a singleton
+let _mixerControl;
+function getMixerControl() {
+    if (_mixerControl)
+        return _mixerControl;
 
-    _init: function() {
-        this.parent('audio-volume-muted', _("Volume"));
+    _mixerControl = new Gvc.MixerControl({ name: 'GNOME Shell Volume Control' });
+    _mixerControl.open();
+
+    return _mixerControl;
+}
 
-        this._control = new Gvc.MixerControl({ name: 'GNOME Shell Volume Control' });
+const VolumeMenu = new Lang.Class({
+    Name: 'VolumeMenu',
+    Extends: PopupMenu.PopupMenuSection,
+
+    _init: function(control) {
+        this.parent();
+
+        this._control = control;
         this._control.connect('state-changed', Lang.bind(this, this._onControlStateChanged));
         this._control.connect('default-sink-changed', Lang.bind(this, this._readOutput));
         this._control.connect('default-source-changed', Lang.bind(this, this._readInput));
@@ -35,10 +48,10 @@ const Indicator = new Lang.Class({
         this._outputSlider = new PopupMenu.PopupSliderMenuItem(0);
         this._outputSlider.connect('value-changed', Lang.bind(this, this._sliderChanged, '_output'));
         this._outputSlider.connect('drag-end', Lang.bind(this, this._notifyVolumeChange));
-        this.menu.addMenuItem(this._outputTitle);
-        this.menu.addMenuItem(this._outputSlider);
+        this.addMenuItem(this._outputTitle);
+        this.addMenuItem(this._outputSlider);
 
-        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
+        this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
 
         this._input = null;
         this._inputVolumeId = 0;
@@ -47,22 +60,11 @@ const Indicator = new Lang.Class({
         this._inputSlider = new PopupMenu.PopupSliderMenuItem(0);
         this._inputSlider.connect('value-changed', Lang.bind(this, this._sliderChanged, '_input'));
         this._inputSlider.connect('drag-end', Lang.bind(this, this._notifyVolumeChange));
-        this.menu.addMenuItem(this._inputTitle);
-        this.menu.addMenuItem(this._inputSlider);
-
-        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
-        this.menu.addSettingsAction(_("Sound Settings"), 'gnome-sound-panel.desktop');
-
-        this.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
-        this._control.open();
-    },
-
-    setLockedState: function(locked) {
-        this.menu.setSettingsVisibility(!locked);
+        this.addMenuItem(this._inputTitle);
+        this.addMenuItem(this._inputSlider);
     },
 
-    _onScrollEvent: function(actor, event) {
-        let direction = event.get_scroll_direction();
+    scroll: function(direction) {
         let currentVolume = this._output.volume;
 
         if (direction == Clutter.ScrollDirection.DOWN) {
@@ -88,9 +90,8 @@ const Indicator = new Lang.Class({
         if (this._control.get_state() == Gvc.MixerControlState.READY) {
             this._readOutput();
             this._readInput();
-            this.actor.show();
         } else {
-            this.actor.hide();
+            this.emit('icon-changed', null);
         }
     },
 
@@ -109,7 +110,7 @@ const Indicator = new Lang.Class({
             this._volumeChanged (null, null, '_output');
         } else {
             this._outputSlider.setValue(0);
-            this.setIcon('audio-volume-muted-symbolic');
+            this.emit('icon-changed', 'audio-volume-muted-symbolic');
         }
     },
 
@@ -196,15 +197,55 @@ const Indicator = new Lang.Class({
         slider.setValue(muted ? 0 : (this[property].volume / this._volumeMax));
         if (property == '_output') {
             if (muted)
-                this.setIcon('audio-volume-muted');
+                this.emit('icon-changed', 'audio-volume-muted');
             else
-                this.setIcon(this._volumeToIcon(this._output.volume));
+                this.emit('icon-changed', this._volumeToIcon(this._output.volume));
         }
     },
 
     _volumeChanged: function(object, param_spec, property) {
         this[property+'Slider'].setValue(this[property].volume / this._volumeMax);
         if (property == '_output' && !this._output.is_muted)
-            this.setIcon(this._volumeToIcon(this._output.volume));
+            this.emit('icon-changed', this._volumeToIcon(this._output.volume));
+    }
+});
+
+const Indicator = new Lang.Class({
+    Name: 'VolumeIndicator',
+    Extends: PanelMenu.SystemStatusButton,
+
+    _init: function() {
+        this.parent('audio-volume-muted', _("Volume"));
+
+        this._isLocked = false;
+
+        this._control = getMixerControl();
+        this._volumeMenu = new VolumeMenu(this._control);
+        this._volumeMenu.connect('icon-changed', Lang.bind(this, function(menu, icon) {
+            this._hasPulseAudio = (icon != null);
+            this.setIcon(icon);
+            this._syncVisibility();
+        }));
+
+        this.menu.addMenuItem(this._volumeMenu);
+
+        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
+        this.menu.addSettingsAction(_("Sound Settings"), 'gnome-sound-panel.desktop');
+
+        this.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
+    },
+
+    setLockedState: function(locked) {
+        this._isLocked = locked;
+        this._syncVisibility();
+    },
+
+    _syncVisibility: function() {
+        this.actor.visible = this._hasPulseAudio && !this._isLocked;
+        this.mainIcon.visible = this._hasPulseAudio;
+    },
+
+    _onScrollEvent: function(actor, event) {
+        this._volumeMenu.scroll(event.get_scroll_direction());
     }
 });